원본 항목: Part 1-1. 삽입 계열 — 7. LDAP 처리CWE-90repo 폴더: sd07_ldapbase/ (빌드·실행은 §3-1)
목표 시간: 1회차 15분 / 2회차 9분 / 3회차 6분
SD05가 “필터”가 오염되는 경우였다면, 이 드릴은 “베이스(검색 스코프)” 가 오염되는 경우다.
치환 규칙이 다르다는 게 핵심 — 값을 검증하는 게 아니라 아예 입력을 받지 않는다.
이 드릴의 문서 구성 (SD01~SD58 공통)
① 요구사항서 — 어떤 코드가 뚫려 있고, 무엇을 만드나.
② pseudo code 템플릿 — Bad 와 Good 의 뼈대이고, 회차마다 가린다.
③ 테스트 코드 — 공격은 막히고 정상은 통과해야 끝이다.
④ 자주 하는 실수
⑤ 모범답안 — 맨 뒤에 접어 둔다.
변형 A/B/C 는 같은 치환 규칙을 다른 소재로 다시 치는 것이다.
0. 이 드릴로 체화할 것
- return mock_ldap_search_base(userSuppliedBase, "manager=m1"); /* base 자체가 외부 입력 */+ return mock_ldap_search_base("ou=NewHires", "manager=m1"); /* base는 상수로 고정 */
SD01·SD05는 “검증 함수를 통과한 값만 조립”이 치환 규칙이었지만, 이 항목은 “애초에 함수가 그 값을 인자로 받지 않는다.” 검증이 아니라 입력 경로 자체를 없애는 것도 유효한(오히려 더 강한) 치환 방식임을 체감한다.
회차별 진행
회차
변형
보는 것
순서
1회차
A 매니저로 팀원 조회
① 요구사항서 ② pseudo code 전체
1. Bad 를 그대로 쳐서 취약점을 관찰한다. 2. Good pseudo 를 한 줄씩 코드로 바꾼다. 3. ③ 테스트를 실행한다.
2회차
B 부서 이동 이력 조회
① 요구사항서 ② pseudo code 의 번호와 제목만 (Level 2)
1. Bad 를 치고 취약점을 확인한다. 2. 번호만 보고 Good 을 쓴다. 3. ③ 테스트를 실행한다.
3회차
C 관리자만 스코프 전환 허용
① 요구사항서만
1. ③ 테스트를 먼저 읽는다. 2. 백지에서 Bad 부터 Good 까지 재현한다.
1회차에 막히면 같은 변형을 한 번 더 친다.
3회차를 통과하면 A 를 백지로 다시 친다.
1. 요구사항서
1-0. 환경 코드 — mock_ldap2 (미리 제공, 타이핑하지 않음)
sd07_ldapbase/src/mock_ldap2.h / mock_ldap2.c — repo에 이미 있다
부서(ou)가 다른 엔트리를 하나 섞어 둔다 — 스코프(base)가 지켜지면 절대 노출되면 안 되는 대상이다.
/* mock_ldap2.h */#ifndef MOCK_LDAP2_H#define MOCK_LDAP2_H/* -1: 잘못된 인자, 0 이상: base(스코프)+filter를 모두 통과한 엔트리 개수 */int mock_ldap_search_base(const char *base, const char *fixed_filter);#endif
int ldap_by_manager_bad(const char *userSuppliedBase);int ldap_by_manager_good(void);
신뢰 경계
값
출처
검증 없이 흘러가는 곳
userSuppliedBase
외부(파일에서 읽은 값 등)
LDAP 검색 스코프(base)
공격 입력표
입력(base)
Bad 결과(매칭 개수)
이유
"ou=NewHires" (정상)
2 (alice, bob)
정상 동작
"*"
3 (alice, bob, carol)
스코프가 무력화되어 다른 부서(Finance)의 carol까지 노출된다
1-B. 변형 B — 부서 이동 이력 조회
base가 "ou=Alumni" 로 바뀌는 버전.
상수가 여러 개일 때 “선택은 인덱스로, 문자열은 내부 테이블에서”라는 원칙(1-2. 외부제어·우회 계열의 9번과 연결)을 적용
1-C. 변형 C — 관리자만 스코프 전환 허용
일반 사용자는 base 고정, 관리자 세션일 때만 미리 정의된 base 목록({"ou=NewHires","ou=Finance"}) 중 하나를 인덱스로 선택하게 허용 — “상수 고정”과 “화이트리스트 인덱스”를 한 드릴에서 함께 연습
2. pseudo code 템플릿 — 보고 쳐서 익힌다
사용법
변형별로 아래 pseudo code 를 소스 파일에 주석만 복사한다.
코드는 주석을 보고 내가 쓴다.
다 쓰면 [ ] 를 x 로 바꾼다.
막히면 §5 모범답안(A)의 해당 단계만 본다.
2-A. ldap_by_manager_good — Level 1 (1회차, 전부 본다)
먼저 Bad 를 그대로 쳐서 §3 의 test_bad_is_vulnerable 이 통과하는지(= 실제로 뚫리는지) 확인한다.
그다음 Good 을 쓴다.
int ldap_by_manager_bad(const char *userSuppliedBase){ /* 1. return mock_ldap_search_base(userSuppliedBase, "manager=m1") * ← base 가 외부 입력 */}int ldap_by_manager_good(void){ /* [ ] 1. 시그니처에서 base 인자를 **없앤다** * (검증이 아니라 입력 경로 제거) */ /* [ ] 2. return mock_ldap_search_base("ou=NewHires", "manager=m1") * base 와 필터는 둘 다 코드 안의 상수 */}
눈여겨볼 점: 코드량으로 보면 이번 드릴이 가장 짧다.
그런데 SD01·SD05처럼 “검증 함수 추가”가 아니라 함수 시그니처 자체를 바꿔서 공격 표면을 없앴다. 코드가 짧다고 체화가 쉬운 게 아니다 — “왜 검증이 아니라 제거인가”를 설명할 수 있어야 이 드릴을 통과한 것이다.